package com.fenqing.comics.utils;

import org.springframework.aop.support.AopUtils;
import org.springframework.context.ApplicationContext;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Spring应用上下文工具类
 *
 * @author fenqing
 */
public class ApplicationContextUtils {

    private static ApplicationContext APPLICATION_CONTEXT;

    private static final ReentrantLock LOCK = new ReentrantLock();

    private static final Condition CONDITION = LOCK.newCondition();

    public static void setApplicationContext(ApplicationContext applicationContext) {
        LOCK.lock();
        try {
            ApplicationContextUtils.APPLICATION_CONTEXT = applicationContext;
            CONDITION.signalAll();
        } finally {
            LOCK.unlock();
        }
    }

    @SuppressWarnings("all")
    public static <T, A extends Annotation> Map<A, T> getBeanMapByAnnotation(Class<A> annotationClass, Class<T> beanClass) {
        readLock();
        Map<String, T> beanMap = ApplicationContextUtils.APPLICATION_CONTEXT.getBeansOfType(beanClass);
        Collection<T> beans = beanMap.values();
        return beans.stream()
                .filter(bean -> ApplicationContextUtils.isAnnotationPresent(bean, annotationClass))
                .collect(Collectors.toMap(
                        bean -> ApplicationContextUtils.getAnnotation(bean, annotationClass),
                        Function.identity(),
                        (k1, k2) -> k2
                ));
    }

    public static boolean isProxy(Object bean) {
        return AopUtils.isAopProxy(bean);
    }

    public static boolean isAnnotationPresent(Object bean, Class<? extends Annotation> annotationClazz) {
        if (isProxy(bean)) {
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            return targetClass.isAnnotationPresent(annotationClazz);
        }
        return bean.getClass().isAnnotationPresent(annotationClazz);
    }

    public static <T  extends Annotation> T getAnnotation(Object bean, Class<T> annotationClazz) {
        if (isProxy(bean)) {
            Class<?> targetClass = AopUtils.getTargetClass(bean);
            return targetClass.getAnnotation(annotationClazz);
        }
        return bean.getClass().getAnnotation(annotationClazz);
    }

    private static void readLock(){
        if (APPLICATION_CONTEXT != null) {
            return;
        }
        LOCK.lock();
        try {
            if (APPLICATION_CONTEXT == null) {
                CONDITION.await();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            LOCK.unlock();
        }

    }

}
